#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <strings.h>
#include "err.hpp"
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <poll.h>
#include <string>

using namespace std;

class tcpserver // 只关心读事件
{
    static const int defaultport = 8080;  // 默认端口号
    static const int defaultbacklog = 10; // TCP链接等待队列的默认大小
    static const int defaultsize = 256;   // fds数组的默认长度
    static const int defaultfd = -1;      // fds[i]的默认文件描述符

public:
    // 构造
    tcpserver(const int port = defaultport, const int size = defaultsize)
        : _listensock(-1), _fds(nullptr), _port(port), _size(size)
    {
    }

    // 析构
    ~tcpserver()
    {
        if (_listensock >= 0)
            close(_listensock);

        if (_fds)
        {
            delete[] _fds;
            _fds = nullptr;
        }
    }

    // 创建套接字
    void Create()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
            exit(SOCKET_CREATE_FAILED);
    }

    // 绑定套接字
    void Bind()
    {
        // 1、初始化属性信息
        struct sockaddr_in infor;
        bzero(&infor, sizeof(infor));
        infor.sin_addr.s_addr = htonl(INADDR_ANY); // ip地址
        infor.sin_port = htons(_port);             // 端口号
        infor.sin_family = AF_INET;                // 通信方式
        // 2、绑定(成功返回0，失败返回-1)
        if (bind(_listensock, (struct sockaddr *)&infor, sizeof(infor)))
            exit(SOCKET_BIND_FAILED);
    }

    // 监听套接字
    void Listen()
    {
        if (listen(_listensock, defaultbacklog)) // 成功返回0，失败返回-1
            exit(SOCKET_LISTEN_FAILED);
    }

    // 初始化服务器
    void init()
    {
        // 1、创建套接字
        Create();
        // 2、绑定套接字
        Bind();
        // 3、监听套接字
        Listen();
        // 4、创建并初始化fds数组
        _fds = new struct pollfd[_size];
        for (int i = 0; i < _size; i++)
        {
            _fds[i].fd = defaultfd;
            _fds[i].events = _fds[i].revents = 0;
        }
    }

    // 获取新连接
    void Accept()
    {
        // 1、获取一个新连接(至少有一个)
        struct sockaddr_in clientinfor;
        socklen_t size = sizeof(clientinfor);
        bzero(&clientinfor, sizeof(clientinfor));
        int sock = accept(_listensock, (struct sockaddr *)&clientinfor, &size);
        // 2、判断是否获取成功
        if (sock < 0)
        {
            cout << "accept failed" << endl;
            exit(SOCK_ACCEPT_FAILED);
        }

        // 2、获取新连接成功，读取客户端信息
        string clientip;
        uint16_t clientport;
        clientip = inet_ntoa(clientinfor.sin_addr); // 将网络32位ip地址转化成本地字符串形式
        clientport = ntohs(clientinfor.sin_port);
        cout << "get a new connect,clientip:" << clientip << ",clientport:" << clientport << endl;

        // 3、将新链接添加到文件描述符集合中，并设置关心读事件
        bool success = false; // 是否成功添加
        for (int i = 0; i < _size; i++)
        {
            // 寻找第一个空位进行插入
            if (_fds[i].fd == defaultfd)
            {
                _fds[i].fd = sock;
                _fds[i].events = POLLIN;
                success = true;
                cout << "新链接添加成功" << endl;
                break;
            }
        }

        if (!success) // 没有添加成功
        {
            cout << "文件描述符数量达到上限，新链接添加失败" << endl;
        }
    }

    // 删除文件描述符
    void delete_fd(int sock)
    {
        for (int i = 0; i < _size; i++)
        {
            if (_fds[i].fd == sock)
                _fds[i].fd = defaultfd;
        }

        close(sock);
    }

    // 读取数据
    void Receive(int sock)
    {
        char buffer[1024];
        int n = recv(sock, buffer, sizeof(buffer) - 1, 0);

        if (n > 0) // 读取成功
        {
            buffer[n] = 0; // 在有效数据后添加一个\0
            cout << "received a message:" << buffer << endl;
            // 再将收到的数据原路写回
            send(sock, buffer, n, 0);
        }
        else if (n == 0) // 写端文件描述符已关闭
        {
            // 先将sock从文件描述符集合中移除，再关闭sock
            delete_fd(sock);
            cout << "client quit" << endl;
        }
        else // 读取出错
        {
            // 先将sock从文件描述符集合中移除，再关闭sock
            delete_fd(sock);
            cout << "recv err" << endl;
            exit(RECV_ERR);
        }
    }

    // 启动服务器(只处理读事件)
    void start()
    {
        // 1、先将监听套接字添加到f文件描述符集合中，并设置关心读事件
        _fds[0].fd = _listensock;
        _fds[0].events = POLLIN;

        // 2、开始监听所有套接字，并处理就绪的文件描述符
        while (true)
        {
            int timeout = 1000; // 等待时间
            int n = poll(_fds, _size, timeout);
            if (n > 0) // 有n个文件描述符就绪
            {
                // 遍历文件描述符集合，找出就绪的文件描述符进行处理
                for (int i = 0; i < _size; i++)
                {
                    // 跳过无效fd
                    if (_fds[i].fd == defaultfd)
                        continue;

                    // 当前sock一定是有效fd，检查sock是否设置关心读事件、且读事件已就绪
                    if ((_fds[i].events & POLLIN) && (_fds[i].revents & POLLIN))
                    {
                        if (_fds[i].fd == _listensock)
                            Accept(); // 获取新连接
                        else
                            Receive(_fds[i].fd); // 读取数据
                    }
                }
            }
            else if (n == 0) // 没有文件描述符就绪，超时返回
            {
                cout << "timeout..." << endl;
            }
            else // 出错
            {
                cout << "poll failed" << endl;
                exit(POLL_FAILED);
            }
        }
    }

private:
    // 禁止拷贝构造
    tcpserver(const tcpserver &) = delete;

    // 禁止赋值重载
    tcpserver &operator=(const tcpserver &) = delete;

private:
    int _listensock;     // 监听描述符
    int _port;           // 端口号
    struct pollfd *_fds; // 文件描述符集合
    int _size;           // 文件描述符集合的容量
};